﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using Transportr.ServiceModel;
using System.IO;

namespace Transportr
{
    public static class Service
    {
        public class ResponseEventArgs : EventArgs
        {
            public ServiceModel.Response Response;
        }

        public class RequestState
        {
            public string Message;
            public Request Request;
            public HttpWebRequest WebRequest;
            public Response Response;
            public HttpWebResponse WebResponse;
            public ResponseCallback Callback;
            public ManualResetEvent Monitor;
        }

        public delegate void ResponseReceivedHandler(object sender, ResponseEventArgs args);
        public delegate void ResponseCallback(object sender, RequestState state);

        public static class Cache
        {
            private static object fileLock = new object();
            private static Dictionary<ServiceModel.Function, int> cacheHits;
            private static Dictionary<ServiceModel.Function, int> cacheMisses;

            public static bool Enabled { get; set; }
            public static string Path { get; set; }

            public class Entry
            {
                [XmlElement]
                public ServiceModel.Response Response;
                [XmlElement]
                public DateTime? LastUpdate;

                public Entry()
                {
                    Response = new Response();
                    LastUpdate = null;
                }
            }

            private static bool toCache(Function f, Parameters p)
            {
                return Enabled;  // TODO
            }

            private static string getCacheName(Function f, Parameters p)
            {
                StringBuilder filename = new StringBuilder(Helper.Functions[f]);
                filename.Append("#");
                if (p.stLat != null && p.stLat.HasValue) filename.Append(p.stLat);
                filename.Append("#");
                if (p.stLng != null && p.stLng.HasValue) filename.Append(p.stLng);
                filename.Append("#");
                if (p.dist != null && p.dist.HasValue) filename.Append(p.dist);
                filename.Append("#");
                if (p.lnId != null && p.lnId.Count > 0) filename.Append(p.lnId[0]);
                filename.Append("#");
                if (p.date != null && p.date.HasValue) filename.Append(p.date);
                return Regex.Replace(filename.ToString(), @"[?:\/*""<>|]", "_");
            }

            public static void Initialize()
            {
                Enabled = false; // TODO
                Path = "ServiceCache";
                cacheHits = new Dictionary<Function, int>();
                cacheMisses = new Dictionary<Function, int>();

                if (!FileStorage.DirectoryExists(Path))
                {
                    FileStorage.CreateDirectory(Path);
                }
            }

            public static bool Check(Function f, Parameters p)
            {
                if (Enabled)
                {
                    bool cached = FileStorage.FileExists(String.Format("{0}\\{1}.xml", Path, getCacheName(f, p)));
                    if (!cached) cacheMisses[f]++;
                    return cached;
                }
                return false;
            }

            public static Entry Get(Function f, Parameters p)
            {
                if (Enabled)
                {
                    try
                    {
                        using (FileStream fs = FileStorage.GetFileStream(String.Format("{0}\\{1}.xml", Path, getCacheName(f, p)),
                                                                         FileMode.Open))
                        {
                            cacheHits[f]++;
                            return (Entry)(new XmlSerializer(typeof(Entry)).Deserialize(fs));
                        }
                    }
                    catch (Exception x)
                    {
                        MessageBox.Show(x.Message);
                        return null;
                    }
                }
                return null;
            }

            public static void Update(Function f, Parameters p, ServiceModel.Response r)
            {
                if (toCache(f, p))
                {
                    try
                    {
                        using (FileStream fs = FileStorage.GetFileStream(String.Format("{0}\\{1}.xml", Path, getCacheName(f, p)),
                                                                         FileMode.Create))
                        {
                            new XmlSerializer(typeof(Entry)).Serialize(fs, r);
                        }
                    }
                    catch (Exception)
                    {
                        System.Diagnostics.Debug.WriteLine("Failed to cache response");
                    }
                }
            }

            public static void Clear()
            {
                if (FileStorage.DirectoryExists(Path))
                {
                    FileStorage.DeleteDirectory(Path);
                    FileStorage.CreateDirectory(Path);
                }
            }
        }

        private static ManualResetEvent done = new ManualResetEvent(false);
        private static Uri Url { get; set; }

        public static bool InProgress { get; private set; }
        public static Request LastRequest { get; private set; }
        public static bool LocalOnly { get; set; }

        public class Request
        {
            public Function Function;
            public Parameters Parameters;
            public Uri Url;
        }

//        public static Dictionary<Function, Slot> Store;

        public class Slot
        {
            private Cache.Entry e;
            public bool Valid { get; private set; }
            public Cache.Entry Entry
            {
                get { return e; }
                set {
                    e = value;
                    if (Received != null)
                    {
                        this.Valid = true;
                        Received(this, new ResponseEventArgs() { Response = e.Response });
                    }
                }
            }
            public event ResponseReceivedHandler Received;

            public Slot()
            {
                e = new Cache.Entry();
                Valid = false;
            }
        }

        public static void Initialize(Uri url)
        {
            Cache.Initialize();
            
            Url = url;
            LocalOnly = false;
//            Store = new Dictionary<Function, Slot>();
            foreach (Function f in Enumerator.GetValues(Function.Lines))
            {
//                Store[f] = new Slot();
            }
        }

        public static bool SendRequest(Function f, Parameters p, ResponseCallback callback, bool force = false)
        {
            Request request = new Request() { Function = f, Parameters = p, Url = generateRequestUrl(f, p) };
            RequestState state = new RequestState() { Request = request, Callback = callback, Monitor = new ManualResetEvent(false) };
            if (Cache.Check(f, p) && !force)
            {
                state.Response = Cache.Get(f, p).Response;

                if (callback != null) callback(new object(), state);
                return true;
            }
            else if (!LocalOnly)
            {
                HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(request.Url);
                httpRequest.Method = "GET";

                state.WebRequest = httpRequest;
                httpRequest.BeginGetResponse(new AsyncCallback(ReceiveResponse), state);
                return true;
            }
            return false;
        }

        private static void ReceiveResponse(IAsyncResult result)
        {
            RequestState state = (RequestState)result.AsyncState;
            HttpWebRequest request = state.WebRequest;
            Stream responseStream = new MemoryStream();
            try
            {
                state.WebResponse = (HttpWebResponse)request.EndGetResponse(result);
                responseStream = state.WebResponse.GetResponseStream();
                state.Response = (Response)(new XmlSerializer(typeof(Response)).Deserialize(responseStream));

                // TODO: CACHE HERE, IF YOU HAVE TO!
            }
            catch (Exception x)
            {
                state.Message = x.Message;
            }
            finally
            {
                responseStream.Close();
                if (state.Callback != null) state.Callback(new object(), state);
                state.Monitor.Set();
            }
        }

        private static Uri generateRequestUrl(Function f, Parameters p)
        {
            StringBuilder requestUrl = new StringBuilder(Url.ToString(), 255);
            requestUrl.Append("?client=1&func=" + Helper.Functions[f]);
            switch (f)
            {
                case Function.StationLines:
                    if (p.stLat.HasValue && p.stLng.HasValue)
                        requestUrl.AppendFormat(ServiceModel.Helper.NumberFormat, "&stLat={0:0.####}&stLng={1:0.####}", p.stLat,
                                                p.stLng);
                    if (p.dist.HasValue)
                        requestUrl.AppendFormat(ServiceModel.Helper.NumberFormat, "&dist={0}", p.dist);
                    break;
                case Function.Lines:
                    break;
                case Function.Terms:
                case Function.LineInfo:
                    if (p.lnId != null && p.lnId.Count == 1)
                        requestUrl.AppendFormat(ServiceModel.Helper.NumberFormat, "&lnId={0}", p.lnId[0]);
                    break;
                case Function.Stations:
                    requestUrl.Append("?func=GetStations");
                    requestUrl.AppendFormat(ServiceModel.Helper.NumberFormat, "&date={0:yyyyMMdd}&stLat={1:0.####}&stLng={2:0.####}",
                                            p.date, p.stLat, p.stLng);
                    foreach (int lnId in p.lnId)
                    {
                        requestUrl.AppendFormat(ServiceModel.Helper.NumberFormat, "&lnId[]={0}", lnId);
                    }
                    break;
                default:
                    System.Diagnostics.Debug.WriteLine("Invalid request.");
                    break;
            }
            return new Uri(requestUrl.ToString());
        }
    }

}
